<!DOCTYPE html>
<html lang=zh-CN>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="google-site-verification" content="xBT4GhYoi5qRD5tr338pgPM5OWHHIDR6mNg1a3euekI" />
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta property="og:description" content="samuel-24276的博客">
    <meta property="og:type" content="website">
    <meta name="description" content="samuel-24276的博客">
    <meta name="keyword"  content="aircloud">
    <link rel="shortcut icon" href="/myblog/img/favicon.ico">

    <title>
        
        信号槽源码解析 - samuel-24276的博客 | samuel-24276&#39;s Blog
        
    </title>

    <!-- Custom CSS -->
    
<link rel="stylesheet" href="/myblog/css/aircloud.css">

    
<link rel="stylesheet" href="/myblog/css/gitment.css">

    <!--<link rel="stylesheet" href="https://imsun.github.io/gitment/style/default.css">-->
    <link href="//at.alicdn.com/t/font_620856_28hi1hpxx24.css" rel="stylesheet" type="text/css">
    <!-- ga & ba script hoook -->
    <script></script>
<meta name="generator" content="Hexo 7.1.1"></head>

<body>

<div class="site-nav-toggle" id="site-nav-toggle">
    <button>
        <span class="btn-bar"></span>
        <span class="btn-bar"></span>
        <span class="btn-bar"></span>
    </button>
</div>

<div class="index-about">
    <i> Like what you do, do what you like </i>
</div>

<div class="index-container">
    
    <div class="index-left">
        
<div class="nav" id="nav">
    <div class="avatar-name">
        <div class="avatar ">
            <img src="/myblog/img/avatar.jpg" />
        </div>
        <div class="name">
            <i>samuel-24276</i>
        </div>
    </div>
    <div class="contents" id="nav-content">
        <ul>
            <li >
                <a href="/myblog/">
                    <i class="iconfont icon-shouye1"></i>
                    <span>主页</span>
                </a>
            </li>
            <li >
                <a href="/myblog/tags">
                    <i class="iconfont icon-biaoqian1"></i>
                    <span>标签</span>
                </a>
            </li>
            <li >
                <a href="/myblog/archives">
                    <i class="iconfont icon-guidang2"></i>
                    <span>存档</span>
                </a>
            </li>
            <li >
                <a href="/myblog/collect/">
                    <i class="iconfont icon-shoucang1"></i>
                    <span>收藏</span>
                </a>
            </li>
            <li >
                <a href="/myblog/about/">
                    <i class="iconfont icon-guanyu2"></i>
                    <span>关于</span>
                </a>
            </li>
            
            <li>
                <a id="search">
                    <i class="iconfont icon-sousuo1"></i>
                    <span>搜索</span>
                </a>
            </li>
            
        </ul>
    </div>
    
        <div id="toc" class="toc-article">
    <ol class="toc"><li class="toc-item toc-level-1"><a class="toc-link" href="#How-Qt-Signals-and-Slots-Work"><span class="toc-text">How Qt Signals and Slots Work</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Signals-and-Slots"><span class="toc-text">Signals and Slots</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#MOC-the-Meta-Object-Compiler"><span class="toc-text">MOC, the Meta Object Compiler</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Magic-Macros"><span class="toc-text">Magic Macros</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#MOC-Generated-Code"><span class="toc-text">MOC Generated Code</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#The-QMetaObject"><span class="toc-text">The QMetaObject</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Introspection-Tables-rocket"><span class="toc-text">Introspection Tables:rocket:</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#String-Table-rocket"><span class="toc-text">String Table:rocket:</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Signals"><span class="toc-text">Signals</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#Calling-a-Slot"><span class="toc-text">Calling a Slot</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#A-Note-About-Indexes-rocket"><span class="toc-text">A Note About Indexes.:rocket:</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#How-Connecting-Works"><span class="toc-text">How Connecting Works.</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Signal-Emission"><span class="toc-text">Signal Emission</span></a></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Qt5-New-Syntax"><span class="toc-text">Qt5 New Syntax</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#New-Syntax-in-Qt5"><span class="toc-text">New Syntax in Qt5</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Why-the-new-syntax"><span class="toc-text">Why the new syntax?</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#New-overloads"><span class="toc-text">New overloads</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Pointer-to-Member-Functions"><span class="toc-text">Pointer to Member Functions</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Type-Traits-QtPrivate-FunctionPointer-rocket"><span class="toc-text">Type Traits: QtPrivate::FunctionPointer:rocket:</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#QObject-connect"><span class="toc-text">QObject::connect</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Signal-Index-fire"><span class="toc-text">Signal Index:fire:</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#The-QSlotObjectBase"><span class="toc-text">The QSlotObjectBase</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Fake-Virtual-Table"><span class="toc-text">Fake Virtual Table</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Protected-Public-or-Private-Signals"><span class="toc-text">Protected, Public, or Private Signals.</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#More-Template-Code"><span class="toc-text">More Template Code</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#Meta-Programming-List-fire"><span class="toc-text">Meta-Programming List:fire:</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#ApplyReturnValue-Trick"><span class="toc-text">ApplyReturnValue Trick</span></a></li></ol></li></ol></li><li class="toc-item toc-level-1"><a class="toc-link" href="#Queued-and-Inter-Thread-Connections"><span class="toc-text">Queued and Inter Thread Connections</span></a><ol class="toc-child"><li class="toc-item toc-level-2"><a class="toc-link" href="#Summary-from-Part-1"><span class="toc-text">Summary from Part 1</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Qt-Event-Loop-fire"><span class="toc-text">Qt Event Loop:fire:</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#Copying-the-parameters"><span class="toc-text">Copying the parameters</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#queued-activate"><span class="toc-text">queued_activate</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#BlockingQueuedConnection"><span class="toc-text">BlockingQueuedConnection</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#The-dangers-of-BlockingQueuedConnection"><span class="toc-text">The dangers of BlockingQueuedConnection</span></a></li></ol></li></ol></li></ol>
</div>
    
</div>


<div class="search-field" id="search-field">
    <div class="search-bg" id="search-bg"></div>
    <div class="search-container">
        <div class="search-input">
            <span id="esc-search"> <i class="icon-fanhui iconfont"></i></span>
            <input id="search-input"/>
            <span id="begin-search">搜索</span>
        </div>
        <div class="search-result-container" id="search-result-container">

        </div>
    </div>
</div>

        <div class="index-about-mobile">
            <i> Like what you do, do what you like </i>
        </div>
    </div>
    
    <div class="index-middle">
        <!-- Main Content -->
        


<div class="post-container">
    <div class="post-title">
        信号槽源码解析
    </div>

    <div class="post-meta">
        <span class="attr">发布于：<span>2023-02-06 20:24:21</span></span>
        
        <span class="attr">标签：/
        
        <a class="tag" href="/myblog/tags/#Qt" title="Qt">Qt</a>
        <span>/</span>
        
        <a class="tag" href="/myblog/tags/#文章翻译" title="文章翻译">文章翻译</a>
        <span>/</span>
        
        
        </span>
        <span class="attr">访问：<span id="busuanzi_value_page_pv"></span>
</span>
</span>
    </div>
    <div class="post-content no-indent">
        <h1 id="How-Qt-Signals-and-Slots-Work"><a href="#How-Qt-Signals-and-Slots-Work" class="headerlink" title="How Qt Signals and Slots Work"></a>How Qt Signals and Slots Work</h1><blockquote>
<p><a target="_blank" rel="noopener" href="https://woboq.com/blog/how-qt-signals-slots-work.html">How Qt Signals and Slots Work (woboq.com)</a></p>
<p><a target="_blank" rel="noopener" href="https://woboq.com/blog/how-qt-signals-slots-work-part2-qt5.html">How Qt Signals and Slots Work - Part 2 - Qt5 New Syntax (woboq.com)</a></p>
<p><a target="_blank" rel="noopener" href="https://woboq.com/blog/how-qt-signals-slots-work-part3-queuedconnection.html">How Qt Signals and Slots Work - Part 3 - Queued and Inter Thread Connections (woboq.com)</a></p>
<p>:fire:信号和槽的连接方式为Auto时，自动选择连接方式：取决于信号emit时的threadID与接收者receiver的threadID是否一致，一致时直接连接，不一致时队列连接。详见下方的Signal Emission</p>
</blockquote>
<p>Qt以其信号和槽机制而闻名。但它是如何工作的呢?在这篇博文中，我们将探索QObject和QMetaObject的内部结构，并揭示信号和插槽是如何工作的。</p>
<p>在这篇博客文章中，我展示了Qt5代码的一部分，有时为了格式和简洁而进行了编辑。</p>
<h2 id="Signals-and-Slots"><a href="#Signals-and-Slots" class="headerlink" title="Signals and Slots"></a>Signals and Slots</h2><p>首先，让我们通过展示官方示例来回顾一下信号和槽的样子。</p>
<p>头文件是这样的:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Counter</span> : <span class="keyword">public</span> QObject</span><br><span class="line">&#123;</span><br><span class="line">    Q_OBJECT</span><br><span class="line">    <span class="type">int</span> m_value;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">value</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> m_value; &#125;</span><br><span class="line"><span class="keyword">public</span> slots:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">setValue</span><span class="params">(<span class="type">int</span> value)</span></span>;</span><br><span class="line">signals:</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">valueChanged</span><span class="params">(<span class="type">int</span> newValue)</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>Somewhere in the <code>.cpp</code> file, we implement <code>setValue()</code></p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Counter::setValue</span><span class="params">(<span class="type">int</span> value)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (value != m_value) &#123;</span><br><span class="line">        m_value = value;</span><br><span class="line">        <span class="function">emit <span class="title">valueChanged</span><span class="params">(value)</span></span>;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>Then one can use this Counter object like this:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Counter a, b;</span><br><span class="line">QObject::<span class="built_in">connect</span>(&amp;a, <span class="built_in">SIGNAL</span>(<span class="built_in">valueChanged</span>(<span class="type">int</span>)),</span><br><span class="line">                   &amp;b, <span class="built_in">SLOT</span>(<span class="built_in">setValue</span>(<span class="type">int</span>)));</span><br><span class="line"></span><br><span class="line">a.<span class="built_in">setValue</span>(<span class="number">12</span>);  <span class="comment">// a.value() == 12, b.value() == 12</span></span><br></pre></td></tr></table></figure>

<p>这是自1992年Qt诞生以来几乎没有改变过的原始语法。</p>
<p>但是，即使基本API从一开始就没有改变，它的实现也已经改变了几次。添加了新功能，并且在引擎盖下发生了很多事情。这并不是什么神奇的事情，这篇博文将告诉你它是如何工作的。</p>
<h2 id="MOC-the-Meta-Object-Compiler"><a href="#MOC-the-Meta-Object-Compiler" class="headerlink" title="MOC, the Meta Object Compiler"></a>MOC, the Meta Object Compiler</h2><p>Qt信号&#x2F;插槽和属性系统基于在运行时**内省(introspect )**对象的能力。内省意味着能够列出一个对象的方法和属性，并拥有关于它们的各种信息，例如它们的参数类型。</p>
<p>如果没有这种能力，QtScript和QML几乎不可能实现。</p>
<p>c++本身不提供内省支持，所以Qt附带了一个工具来提供它。这个工具就是MOC。它是一个代码生成器(而不是一些人所说的预处理器)。</p>
<p>它解析头文件并生成一个附加的c++文件，该文件与程序的其余部分一起编译。生成的c++文件包含自省所需的所有信息。</p>
<p>Qt有时会因为这个额外的代码生成器而受到语言纯粹主义者的批评。我将让Qt文档来回应这个批评。代码生成器没有任何问题，MOC是一个很大的帮助。</p>
<h2 id="Magic-Macros"><a href="#Magic-Macros" class="headerlink" title="Magic Macros"></a>Magic Macros</h2><p>你能找出不是纯c++关键字的关键字吗?<code>signals, slots, Q_OBJECT, emit, SIGNAL, SLOT</code>。这些被称为c++的Qt扩展。它们实际上是在qobjectdef .h中定义的简单宏</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> signals public</span></span><br><span class="line"><span class="meta">#<span class="keyword">define</span> slots <span class="comment">/* nothing */</span></span></span><br></pre></td></tr></table></figure>

<p>没错，信号和槽都是简单的函数:编译器会像处理其他函数一样处理它们。宏仍然有一个目的:MOC会看到它们。</p>
<p>Qt4及之前对信号进行保护。为了启用新语法，它们在Qt5中被公开。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> Q_OBJECT \</span></span><br><span class="line"><span class="meta">public: \</span></span><br><span class="line"><span class="meta">    static const QMetaObject staticMetaObject; \</span></span><br><span class="line"><span class="meta">    virtual const QMetaObject *metaObject() const; \</span></span><br><span class="line"><span class="meta">    virtual void *qt_metacast(const char *); \</span></span><br><span class="line"><span class="meta">    virtual int qt_metacall(QMetaObject::Call, int, void **); \</span></span><br><span class="line"><span class="meta">    QT_TR_FUNCTIONS <span class="comment">/* translations helper */</span> \</span></span><br><span class="line"><span class="meta">private: \</span></span><br><span class="line"><span class="meta">    Q_DECL_HIDDEN static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **);</span></span><br></pre></td></tr></table></figure>

<p><code>Q_OBJECT</code>定义了一堆函数和一个静态<code>QMetaObject</code>，这些函数在MOC生成的文件中实现。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">define</span> emit <span class="comment">/* nothing */</span></span></span><br></pre></td></tr></table></figure>

<p><code>emit</code>是一个空宏。它甚至没有被MOC解析。换句话说，emit只是可选的，没有任何意义(除了给开发人员一个提示)。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function">Q_CORE_EXPORT <span class="type">const</span> <span class="type">char</span> *<span class="title">qFlagLocation</span><span class="params">(<span class="type">const</span> <span class="type">char</span> *method)</span></span>;</span><br><span class="line"><span class="meta">#<span class="keyword">ifndef</span> QT_NO_DEBUG</span></span><br><span class="line"><span class="meta"># <span class="keyword">define</span> QLOCATION <span class="string">&quot;\0&quot;</span> __FILE__ <span class="string">&quot;:&quot;</span> QTOSTRING(__LINE__)</span></span><br><span class="line"><span class="meta"># <span class="keyword">define</span> SLOT(a)     qFlagLocation(<span class="string">&quot;1&quot;</span>#a QLOCATION)</span></span><br><span class="line"><span class="meta"># <span class="keyword">define</span> SIGNAL(a)   qFlagLocation(<span class="string">&quot;2&quot;</span>#a QLOCATION)</span></span><br><span class="line"><span class="meta">#<span class="keyword">else</span></span></span><br><span class="line"><span class="meta"># <span class="keyword">define</span> SLOT(a)     <span class="string">&quot;1&quot;</span>#a</span></span><br><span class="line"><span class="meta"># <span class="keyword">define</span> SIGNAL(a)   <span class="string">&quot;2&quot;</span>#a</span></span><br><span class="line"><span class="meta">#<span class="keyword">endif</span></span></span><br></pre></td></tr></table></figure>

<p><strong>这些宏只是使用预处理器将参数转换为字符串，并在前面添加代码。</strong></p>
<p><strong>在调试模式下，我们还用文件位置注释字符串，以便在信号连接失败时发出警告消息。这是在Qt 4.5中以兼容的方式添加的。为了知道哪些字符串有行信息，我们使用qFlagLocation，它将在一个有两个条目的表中注册字符串地址</strong>。</p>
<h2 id="MOC-Generated-Code"><a href="#MOC-Generated-Code" class="headerlink" title="MOC Generated Code"></a>MOC Generated Code</h2><p>我们现在将讨论在Qt5中由moc生成的部分代码。</p>
<h3 id="The-QMetaObject"><a href="#The-QMetaObject" class="headerlink" title="The QMetaObject"></a>The QMetaObject</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//staticMetaObject构造一个QMetaObject对象</span></span><br><span class="line"><span class="type">const</span> QMetaObject Counter::staticMetaObject = &#123;</span><br><span class="line">    &#123; &amp;QObject::staticMetaObject, qt_meta_stringdata_Counter.data,</span><br><span class="line">      qt_meta_data_Counter,  qt_static_metacall, Q_NULLPTR, Q_NULLPTR&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">const</span> QMetaObject *<span class="title">Counter::metaObject</span><span class="params">()</span> <span class="type">const</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> QObject::d_ptr-&gt;metaObject ? QObject::d_ptr-&gt;<span class="built_in">dynamicMetaObject</span>() : &amp;staticMetaObject;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>我们在这里看到Counter::metaObject()和Counter::staticMetaObject的实现。它们在Q_OBJECT宏中声明。<code>QObject::d_ptr-&gt;metaObject</code>只用于动态元对象(QML对象)，所以**一般来说，虚函数metaObject()只返回类的<code>staticMetaObject</code>**。</p>
<p>staticMetaObject在只读数据中构造。qobjectdef .h中定义的QMetaObject如下所示:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">QMetaObject</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="comment">/* ... Skiped all the public functions ... */</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">enum</span> <span class="title class_">Call</span> &#123; InvokeMetaMethod, ReadProperty, WriteProperty, <span class="comment">/*...*/</span> &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">struct</span> &#123; <span class="comment">// private data</span></span><br><span class="line">        <span class="type">const</span> QMetaObject *superdata;</span><br><span class="line">        <span class="type">const</span> QByteArrayData *stringdata;</span><br><span class="line">        <span class="type">const</span> uint *data;</span><br><span class="line">        <span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(*StaticMetacallFunction)</span><span class="params">(QObject *, QMetaObject::Call, <span class="type">int</span>, <span class="type">void</span> **)</span></span>;</span><br><span class="line">        StaticMetacallFunction static_metacall;</span><br><span class="line">        <span class="type">const</span> QMetaObject **relatedMetaObjects;</span><br><span class="line">        <span class="type">void</span> *extradata; <span class="comment">//reserved for future use</span></span><br><span class="line">    &#125; d;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>d的存在是为了<strong>象征</strong>所有成员都应该是私有的。它们不是私有的，以保持POD并允许静态初始化。</p>
<p>QMetaObject使用父对象的元对象(在本例中是QObject::staticMetaObject)作为superdata进行初始化。stringdata和data使用本文后面解释的一些数据进行初始化。static_metacall是一个初始化为Counter::qt_static_metacall的函数指针。</p>
<h3 id="Introspection-Tables-rocket"><a href="#Introspection-Tables-rocket" class="headerlink" title="Introspection Tables:rocket:"></a>Introspection Tables:rocket:</h3><p>首先，让我们分析QMetaObject的整数数据。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">static</span> <span class="type">const</span> uint qt_meta_data_Counter[] = &#123;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// content:</span></span><br><span class="line">       <span class="number">7</span>,       <span class="comment">// revision</span></span><br><span class="line">       <span class="number">0</span>,       <span class="comment">// classname</span></span><br><span class="line">       <span class="number">0</span>,    <span class="number">0</span>, <span class="comment">// classinfo</span></span><br><span class="line">       <span class="number">2</span>,   <span class="number">14</span>, <span class="comment">// methods</span></span><br><span class="line">       <span class="number">0</span>,    <span class="number">0</span>, <span class="comment">// properties</span></span><br><span class="line">       <span class="number">0</span>,    <span class="number">0</span>, <span class="comment">// enums/sets</span></span><br><span class="line">       <span class="number">0</span>,    <span class="number">0</span>, <span class="comment">// constructors</span></span><br><span class="line">       <span class="number">0</span>,       <span class="comment">// flags</span></span><br><span class="line">       <span class="number">1</span>,       <span class="comment">// signalCount</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// signals: name, argc, parameters, tag, flags</span></span><br><span class="line">       <span class="number">1</span>,    <span class="number">1</span>,   <span class="number">24</span>,    <span class="number">2</span>, <span class="number">0x06</span> <span class="comment">/* Public */</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// slots: name, argc, parameters, tag, flags</span></span><br><span class="line">       <span class="number">4</span>,    <span class="number">1</span>,   <span class="number">27</span>,    <span class="number">2</span>, <span class="number">0x0a</span> <span class="comment">/* Public */</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// signals: parameters</span></span><br><span class="line">    QMetaType::Void, QMetaType::Int,    <span class="number">3</span>,</span><br><span class="line"></span><br><span class="line"> <span class="comment">// slots: parameters</span></span><br><span class="line">    QMetaType::Void, QMetaType::Int,    <span class="number">5</span>,</span><br><span class="line"></span><br><span class="line">       <span class="number">0</span>        <span class="comment">// eod</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>header由前13个<code>int</code>组成。当header有两列时，第一列是count，第二列是该数组中描述开始的索引（下标）。<br>这个case实例里我们有两个方法，方法描述在下标14处开始。</p>
<p>方法描述由5个<code>int</code>组成。第一个是名称name，它是字符串表中的索引(我们将在后面查看详细信息)。第二个整数是参数的数量argc，后面是可以找到参数描述的索引。我们现在将忽略标记tag和标志flags。对于每个函数，moc还保存每个参数的类型，函数返回类型和名称的索引。</p>
<h3 id="String-Table-rocket"><a href="#String-Table-rocket" class="headerlink" title="String Table:rocket:"></a>String Table:rocket:</h3><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">qt_meta_stringdata_Counter_t</span> &#123;</span><br><span class="line">	QByteArrayData data[<span class="number">6</span>];</span><br><span class="line">    <span class="type">char</span> stringdata0[<span class="number">46</span>];</span><br><span class="line">&#125;;</span><br><span class="line"><span class="meta">#<span class="keyword">define</span> QT_MOC_LITERAL(idx, ofs, len) \</span></span><br><span class="line"><span class="meta">    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \</span></span><br><span class="line"><span class="meta">    qptrdiff(offsetof(qt_meta_stringdata_Counter_t, stringdata0) + ofs \</span></span><br><span class="line"><span class="meta">        - idx * sizeof(QByteArrayData)) \</span></span><br><span class="line"><span class="meta">    )</span></span><br><span class="line"><span class="type">static</span> <span class="type">const</span> qt_meta_stringdata_Counter_t qt_meta_stringdata_Counter = &#123;</span><br><span class="line">    &#123;</span><br><span class="line">        <span class="comment">//QT_MOC_LITERAL(，下方数组的起始下标，长度)</span></span><br><span class="line"><span class="built_in">QT_MOC_LITERAL</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">7</span>), <span class="comment">// &quot;Counter&quot;</span></span><br><span class="line"><span class="built_in">QT_MOC_LITERAL</span>(<span class="number">1</span>, <span class="number">8</span>, <span class="number">12</span>), <span class="comment">// &quot;valueChanged&quot;</span></span><br><span class="line"><span class="built_in">QT_MOC_LITERAL</span>(<span class="number">2</span>, <span class="number">21</span>, <span class="number">0</span>), <span class="comment">// &quot;&quot;</span></span><br><span class="line"><span class="built_in">QT_MOC_LITERAL</span>(<span class="number">3</span>, <span class="number">22</span>, <span class="number">8</span>), <span class="comment">// &quot;newValue&quot;</span></span><br><span class="line"><span class="built_in">QT_MOC_LITERAL</span>(<span class="number">4</span>, <span class="number">31</span>, <span class="number">8</span>), <span class="comment">// &quot;setValue&quot;</span></span><br><span class="line"><span class="built_in">QT_MOC_LITERAL</span>(<span class="number">5</span>, <span class="number">40</span>, <span class="number">5</span>) <span class="comment">// &quot;value&quot;</span></span><br><span class="line"></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="string">&quot;Counter\0valueChanged\0\0newValue\0setValue\0&quot;</span></span><br><span class="line">    <span class="string">&quot;value&quot;</span></span><br><span class="line">&#125;;</span><br><span class="line"><span class="meta">#<span class="keyword">undef</span> QT_MOC_LITERAL</span></span><br></pre></td></tr></table></figure>

<p>这基本上是一个QByteArray的静态数组。<strong>QT_MOC_LITERAL宏创建一个静态QByteArray，它引用下面字符串中的特定索引</strong>。</p>
<h3 id="Signals"><a href="#Signals" class="headerlink" title="Signals"></a>Signals</h3><p>MOC还实现了这些信号。它们是简单的函数，只是创建一个指向参数_t1的指针数组，并将其传递给QMetaObject::activate。数组的第一个元素是返回值。在我们的例子中，它是0，因为返回值是void。</p>
<p>传递给activate的第三个参数是信号索引(在本例中为0)。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SIGNAL 0</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Counter::valueChanged</span><span class="params">(<span class="type">int</span> _t1)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">void</span> *_a[] = &#123; Q_NULLPTR, <span class="built_in">const_cast</span>&lt;<span class="type">void</span>*&gt;(<span class="built_in">reinterpret_cast</span>&lt;<span class="type">const</span> <span class="type">void</span>*&gt;(&amp;_t1)) &#125;;</span><br><span class="line">    QMetaObject::<span class="built_in">activate</span>(<span class="keyword">this</span>, &amp;staticMetaObject, <span class="number">0</span>, _a);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h3 id="Calling-a-Slot"><a href="#Calling-a-Slot" class="headerlink" title="Calling a Slot"></a>Calling a Slot</h3><p>也可以通过qt_static_metacall函数中的索引来调用slot:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Counter::qt_static_metacall</span><span class="params">(QObject *_o, QMetaObject::Call _c, <span class="type">int</span> _id, <span class="type">void</span> **_a)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (_c == QMetaObject::InvokeMetaMethod) &#123;</span><br><span class="line">        <span class="built_in">Q_ASSERT</span>(staticMetaObject.<span class="built_in">cast</span>(_o));</span><br><span class="line">        Counter *<span class="type">_t</span> = <span class="built_in">static_cast</span>&lt;Counter *&gt;(_o);</span><br><span class="line">        <span class="built_in">Q_UNUSED</span>(<span class="type">_t</span>)</span><br><span class="line">        <span class="keyword">switch</span> (_id) &#123;</span><br><span class="line">        <span class="keyword">case</span> <span class="number">0</span>: <span class="type">_t</span>-&gt;<span class="built_in">valueChanged</span>((*<span class="built_in">reinterpret_cast</span>&lt; <span class="built_in">int</span>(*)&gt;(_a[<span class="number">1</span>]))); <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">case</span> <span class="number">1</span>: <span class="type">_t</span>-&gt;<span class="built_in">setValue</span>((*<span class="built_in">reinterpret_cast</span>&lt; <span class="built_in">int</span>(*)&gt;(_a[<span class="number">1</span>]))); <span class="keyword">break</span>;</span><br><span class="line">        <span class="keyword">default</span>: ;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>

<p>指向参数的数组指针的格式与用于信号的数组指针(valueChanged里的_a)的格式相同。_a[0]不会被修改，因为这里的所有内容都返回void。</p>
<h2 id="A-Note-About-Indexes-rocket"><a href="#A-Note-About-Indexes-rocket" class="headerlink" title="A Note About Indexes.:rocket:"></a>A Note About Indexes.:rocket:</h2><p>在每个QMetaObject中，该对象的槽、信号和其他可调用方法都给出了从0开始的索引。<strong>它们在moc文件中的顺序是，信号先出现，然后是槽，然后是其他方法</strong>。这个索引在内部称为相对索引。它们不包括父母的索引。</p>
<p>但一般来说，我们不想知道一个更全局的索引，它不是相对于某个特定的类，而是包括继承链中的所有其他方法。<strong>我们只需要在相对下标上加一个偏移量就能得到绝对下标。它是公共API中使用的索引</strong>，由<code>QMetaObject::indexOf&#123;Signal,Slot,Method&#125;</code>等函数返回。</p>
<p>连接机制使用一个按信号索引的vector。但是所有的槽浪费了矢量vector中的空间，而且一个对象中的槽通常比信号多。因此，从Qt 4.6开始，使用了一个新的内部<strong>signal index</strong>，它只包含使用的信号索引。</p>
<p>在使用Qt进行开发时，您只需要了解绝对方法索引。但是在浏览Qt的QObject源代码时，你必须意识到这三者（相对&#x2F;绝对索引，信号索引）之间的区别。</p>
<h2 id="How-Connecting-Works"><a href="#How-Connecting-Works" class="headerlink" title="How Connecting Works."></a>How Connecting Works.</h2><p>Qt在进行连接时要做的第一件事是找出信号和插槽的索引。Qt将在元对象的字符串表（String Table）中查找相应的索引。</p>
<p>然后创建一个QObjectPrivate::Connection对象并将其添加到内部链表中。</p>
<p>每个连接需要存储哪些信息?我们需要一种方法来快速访问给定信号索引的连接。由于可以有多个插槽连接到同一个信号，我们需要每个信号都有一个连接插槽的列表。每个连接必须包含接收者对象和插槽的索引。我们还希望在销毁接收器时自动销毁连接，这样每个接收器(receiver)对象都需要知道谁(sender)与他连接，以便他可以清除连接。</p>
<p>Here is the <code>QObjectPrivate::Connection</code> as defined in <a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobject_p.h.html#QObjectPrivate::Connection">qobject_p.h</a> :</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">QObjectPrivate</span>::Connection</span><br><span class="line">&#123;</span><br><span class="line">    QObject *sender;</span><br><span class="line">    QObject *receiver;</span><br><span class="line">    <span class="keyword">union</span> &#123;</span><br><span class="line">        StaticMetaCallFunction callFunction;</span><br><span class="line">        QtPrivate::QSlotObjectBase *slotObj;</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="comment">// The next pointer for the singly-linked ConnectionList</span></span><br><span class="line">    Connection *nextConnectionList;</span><br><span class="line">    <span class="comment">//senders linked list</span></span><br><span class="line">    Connection *next;</span><br><span class="line">    Connection **prev;</span><br><span class="line">    QAtomicPointer&lt;<span class="type">const</span> <span class="type">int</span>&gt; argumentTypes;</span><br><span class="line">    QAtomicInt ref_;</span><br><span class="line">    ushort method_offset;</span><br><span class="line">    ushort method_relative;</span><br><span class="line">    uint signal_index : <span class="number">27</span>; <span class="comment">// In signal range (see QObjectPrivate::signalIndex())</span></span><br><span class="line">    ushort connectionType : <span class="number">3</span>; <span class="comment">// 0 == auto, 1 == direct, 2 == queued, 4 == blocking</span></span><br><span class="line">    ushort isSlotObject : <span class="number">1</span>;</span><br><span class="line">    ushort ownArgumentTypes : <span class="number">1</span>;</span><br><span class="line">    <span class="built_in">Connection</span>() : <span class="built_in">nextConnectionList</span>(<span class="number">0</span>), <span class="built_in">ref_</span>(<span class="number">2</span>), <span class="built_in">ownArgumentTypes</span>(<span class="literal">true</span>) &#123;</span><br><span class="line">        <span class="comment">//ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection</span></span><br><span class="line">    &#125;</span><br><span class="line">    ~<span class="built_in">Connection</span>();</span><br><span class="line">    <span class="function"><span class="type">int</span> <span class="title">method</span><span class="params">()</span> <span class="type">const</span> </span>&#123; <span class="keyword">return</span> method_offset + method_relative; &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">ref</span><span class="params">()</span> </span>&#123; ref_.<span class="built_in">ref</span>(); &#125;</span><br><span class="line">    <span class="function"><span class="type">void</span> <span class="title">deref</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        <span class="keyword">if</span> (!ref_.<span class="built_in">deref</span>()) &#123;</span><br><span class="line">            <span class="built_in">Q_ASSERT</span>(!receiver);</span><br><span class="line">            <span class="keyword">delete</span> <span class="keyword">this</span>;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>每个对象都有一个连接向量:它是一个为每个信号关联QObjectPrivate:: connection链表的向量。<br>每个对象还有一个反向的连接列表，用于自动删除该对象所连接的连接。它是一个双重链表。</p>
<p><img src="https://woboq.com/blog/how-qt-signals-slots-work/qobject_connection.png" alt="QObjectPrivate::Connection"></p>
<p>使用链表是因为它们允许快速添加和删除对象。它们是通过在QObjectPrivate::Connection中使用指向下一个&#x2F;上一个节点的指针实现的。<br>注意，senderList的prev指针是指向指针的指针。这是因为我们并没有真正指向前一个节点，而是指向前一个节点中下一个节点的指针。此指针仅在连接被破坏时使用，而不是用于向后迭代。它允许第一项没有特殊情况。</p>
<p><img src="https://woboq.com/blog/how-qt-signals-slots-work/qobject_connection_node.png"></p>
<h2 id="Signal-Emission"><a href="#Signal-Emission" class="headerlink" title="Signal Emission"></a>Signal Emission</h2><blockquote>
<p>信号发射流程：</p>
<p>计算信号索引signal_index ，若该索引处的信号未被槽函数连接，直接返回。</p>
<p>信号槽的连接是线程安全的，对发送者sender上锁，拿到sender在signal_index处的 QObjectPrivate::ConnectionList *list；</p>
<p>QObjectPrivate::Connection* c &#x3D; list-&gt;first；</p>
<p>QObjectPrivate::Connection *last &#x3D; list-&gt;last;&#x2F;&#x2F; 检查这里的last，以确保在信号发射期间添加的信号不会在此发射中发射。</p>
<p>在do while()循环中继续其他操作：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">do</span> &#123;&#125; <span class="keyword">while</span> (c != last &amp;&amp; (c = c-&gt;nextConnectionList) != <span class="number">0</span>);</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<p>若连接c的接收者为空，跳过。</p>
<p>获取接收者的threadID，并于当前的threadID比较，比较结果为receiverInSameThread 。</p>
<p>若自动连接且!receiverInSameThread ，或队列连接，则queued_activate()，跳过；若阻塞连接，代码略，跳过。</p>
<p>设置sender，并在超出作用域时将其复位。</p>
<p>获取连接c的callFunction，若c为槽函数对象，执行槽函数处理；</p>
<p>若callFunction不空，则调用它。还需要检查保存的metodOffset是否仍然有效(可以从析构函数调用)；</p>
<p>否则，动态对象回退，调用metacall()。</p>
<p>检查该对象是否被槽函数删除。</p>
</blockquote>
<p>当我们调用一个信号时，我们已经看到它调用MOC生成的代码，该代码调用QMetaObject::activate。</p>
<p>下面是qobject.cpp实现的一个带注释的版本：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">QMetaObject::activate</span><span class="params">(QObject *sender, <span class="type">const</span> QMetaObject *m, <span class="type">int</span> local_signal_index,</span></span></span><br><span class="line"><span class="params"><span class="function">                           <span class="type">void</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">activate</span>(sender, QMetaObjectPrivate::<span class="built_in">signalOffset</span>(m), local_signal_index, argv);</span><br><span class="line">    <span class="comment">/* We just forward to the next function here. We pass the signal offset of</span></span><br><span class="line"><span class="comment">     * the meta object rather than the QMetaObject itself</span></span><br><span class="line"><span class="comment">     * It is split into two functions because QML internals will call the later. */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">QMetaObject::activate</span><span class="params">(QObject *sender, <span class="type">int</span> signalOffset, <span class="type">int</span> local_signal_index, <span class="type">void</span> **argv)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">int</span> signal_index = signalOffset + local_signal_index;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* The first thing we do is quickly check a bit-mask of 64 bits. If it is 0,</span></span><br><span class="line"><span class="comment">     * we are sure there is nothing connected to this signal, and we can return</span></span><br><span class="line"><span class="comment">     * quickly, which means emitting a signal connected to no slot is extremely</span></span><br><span class="line"><span class="comment">     * fast. */</span></span><br><span class="line">    <span class="keyword">if</span> (!sender-&gt;<span class="built_in">d_func</span>()-&gt;<span class="built_in">isSignalConnected</span>(signal_index))</span><br><span class="line">        <span class="keyword">return</span>; <span class="comment">// nothing connected to these signals, and no spy</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* ... Skipped some debugging and QML hooks, and some sanity check ... */</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">/* We lock a mutex because all operations in the connectionLists are thread safe */</span></span><br><span class="line">    <span class="function">QMutexLocker <span class="title">locker</span><span class="params">(signalSlotLock(sender))</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Get the ConnectionList for this signal.  I simplified a bit here. The real code</span></span><br><span class="line"><span class="comment">     * also refcount the list and do sanity checks */</span></span><br><span class="line">    QObjectConnectionListVector *connectionLists = sender-&gt;<span class="built_in">d_func</span>()-&gt;connectionLists;</span><br><span class="line">    <span class="type">const</span> QObjectPrivate::ConnectionList *list =</span><br><span class="line">        &amp;connectionLists-&gt;<span class="built_in">at</span>(signal_index);</span><br><span class="line"></span><br><span class="line">    QObjectPrivate::Connection *c = list-&gt;first;</span><br><span class="line">    <span class="keyword">if</span> (!c) <span class="keyword">continue</span>;</span><br><span class="line">    <span class="comment">// We need to check against last here to ensure that signals added</span></span><br><span class="line">    <span class="comment">// during the signal emission are not emitted in this emission.</span></span><br><span class="line">    QObjectPrivate::Connection *last = list-&gt;last;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/* Now iterates, for each slot */</span></span><br><span class="line">    <span class="keyword">do</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!c-&gt;receiver)</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line"></span><br><span class="line">        QObject * <span class="type">const</span> receiver = c-&gt;receiver;</span><br><span class="line">        <span class="type">const</span> <span class="type">bool</span> receiverInSameThread = QThread::<span class="built_in">currentThreadId</span>() == receiver-&gt;<span class="built_in">d_func</span>()-&gt;threadData-&gt;threadId;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// determine if this connection should be sent immediately or</span></span><br><span class="line">        <span class="comment">// put into the event queue</span></span><br><span class="line">        <span class="keyword">if</span> ((c-&gt;connectionType == Qt::AutoConnection &amp;&amp; !receiverInSameThread)</span><br><span class="line">            || (c-&gt;connectionType == Qt::QueuedConnection)) &#123;</span><br><span class="line">            <span class="comment">/* Will basically copy the argument and post an event */</span></span><br><span class="line">            <span class="built_in">queued_activate</span>(sender, signal_index, c, argv);</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (c-&gt;connectionType == Qt::BlockingQueuedConnection) &#123;</span><br><span class="line">            <span class="comment">/* ... Skipped ... */</span></span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">/* Helper struct that sets the sender() (and reset it backs when it</span></span><br><span class="line"><span class="comment">         * goes out of scope */</span></span><br><span class="line">        QConnectionSenderSwitcher sw;</span><br><span class="line">        <span class="keyword">if</span> (receiverInSameThread)</span><br><span class="line">            sw.switchSender(receiver, sender, signal_index);</span><br><span class="line"></span><br><span class="line">        <span class="type">const</span> QObjectPrivate::StaticMetaCallFunction callFunction = c-&gt;callFunction;</span><br><span class="line">        <span class="type">const</span> <span class="type">int</span> method_relative = c-&gt;method_relative;</span><br><span class="line">        <span class="keyword">if</span> (c-&gt;isSlotObject) &#123;</span><br><span class="line">            <span class="comment">/* ... Skipped....  Qt5-style connection to function pointer */</span></span><br><span class="line">        &#125; <span class="keyword">else</span> <span class="keyword">if</span> (callFunction &amp;&amp; c-&gt;method_offset &lt;= receiver-&gt;<span class="built_in">metaObject</span>()-&gt;<span class="built_in">methodOffset</span>()) &#123;</span><br><span class="line">            <span class="comment">/* If we have a callFunction (a pointer to the qt_static_metacall</span></span><br><span class="line"><span class="comment">             * generated by moc) we will call it. We also need to check the</span></span><br><span class="line"><span class="comment">             * saved metodOffset is still valid (we could be called from the</span></span><br><span class="line"><span class="comment">             * destructor) */</span></span><br><span class="line">            locker.<span class="built_in">unlock</span>(); <span class="comment">// We must not keep the lock while calling use code</span></span><br><span class="line">            <span class="built_in">callFunction</span>(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);</span><br><span class="line">            locker.<span class="built_in">relock</span>();</span><br><span class="line">        &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">            <span class="comment">/* Fallback for dynamic objects */</span></span><br><span class="line">            <span class="type">const</span> <span class="type">int</span> method = method_relative + c-&gt;method_offset;</span><br><span class="line">            locker.<span class="built_in">unlock</span>();</span><br><span class="line">            <span class="built_in">metacall</span>(receiver, QMetaObject::InvokeMetaMethod, method, argv);</span><br><span class="line">            locker.<span class="built_in">relock</span>();</span><br><span class="line">        &#125;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// Check if the object was not deleted by the slot</span></span><br><span class="line">        <span class="keyword">if</span> (connectionLists-&gt;orphaned) <span class="keyword">break</span>;</span><br><span class="line">    &#125; <span class="keyword">while</span> (c != last &amp;&amp; (c = c-&gt;nextConnectionList) != <span class="number">0</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h1 id="Qt5-New-Syntax"><a href="#Qt5-New-Syntax" class="headerlink" title="Qt5 New Syntax"></a>Qt5 New Syntax</h1><h2 id="New-Syntax-in-Qt5"><a href="#New-Syntax-in-Qt5" class="headerlink" title="New Syntax in Qt5"></a>New Syntax in Qt5</h2><p>The new syntax looks like this:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">QObject::<span class="built_in">connect</span>(&amp;a, &amp;Counter::valueChanged,</span><br><span class="line">                 &amp;b, &amp;Counter::setValue);</span><br></pre></td></tr></table></figure>

<h3 id="Why-the-new-syntax"><a href="#Why-the-new-syntax" class="headerlink" title="Why the new syntax?"></a>Why the new syntax?</h3><p>我已经在一篇专门的博客文章中解释了新语法的优点。总之，新语法允许对信号和槽进行<strong>编译时检查</strong>。它还允许<strong>自动转换参数</strong>，如果它们没有相同的类型。作为奖励，它支持<strong>lambda表达式</strong>。</p>
<h2 id="New-overloads"><a href="#New-overloads" class="headerlink" title="New overloads"></a>New overloads</h2><p>为了实现这一点，只需要做一些改变。<br>主要思想是对QObject::connect进行新的重载，将函数指针作为参数而不是char*</p>
<p>QObject::connect有三个新的静态重载(不是实际代码)：</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">QObject::<span class="built_in">connect</span>(<span class="type">const</span> QObject *sender, PointerToMemberFunction signal,</span><br><span class="line">                 <span class="type">const</span> QObject *receiver, PointerToMemberFunction slot,</span><br><span class="line">                 Qt::ConnectionType type)</span><br><span class="line">QObject::<span class="built_in">connect</span>(<span class="type">const</span> QObject *sender, PointerToMemberFunction signal,</span><br><span class="line">                 PointerToFunction method)</span><br><span class="line">QObject::<span class="built_in">connect</span>(<span class="type">const</span> QObject *sender, PointerToMemberFunction signal,</span><br><span class="line">                 Functor method)</span><br></pre></td></tr></table></figure>

<p>第一种更接近于旧语法:将来自发送方的信号连接到接收方对象中的插槽。另外两个重载是将信号连接到没有接收器的静态函数或仿函对象。</p>
<p>它们非常相似，在本文中我们只分析第一个。</p>
<h2 id="Pointer-to-Member-Functions"><a href="#Pointer-to-Member-Functions" class="headerlink" title="Pointer to Member Functions"></a>Pointer to Member Functions</h2><p>在继续解释之前，我想先开一个括号，谈谈指向成员函数的指针。</p>
<p>下面是一个简单的示例代码，声明一个指向成员函数的指针并调用它。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">void</span> (QPoint::*myFunctionPtr)(<span class="type">int</span>); <span class="comment">// Declares myFunctionPtr as a pointer to a member function returning void and taking (int) as parameter</span></span><br><span class="line">myFunctionPtr = &amp;QPoint::setX;</span><br><span class="line">QPoint p;</span><br><span class="line">QPoint *pp = &amp;p;</span><br><span class="line">(p.*myFunctionPtr)(<span class="number">5</span>); <span class="comment">// calls p.setX(5);</span></span><br><span class="line">(pp-&gt;*myFunctionPtr)(<span class="number">5</span>); <span class="comment">// calls pp-&gt;setX(5);</span></span><br></pre></td></tr></table></figure>

<p>指向成员的指针和指向成员函数的指针通常是c++子集的一部分，使用不多，因此不太为人所知。</p>
<p>好消息是，在使用Qt及其新语法时，您仍然不需要对它们了解太多。所有你需要记住的是在连接调用中把&amp;放在信号名称之前。但是**您不需要处理::*、.*或-&gt;*加密操作符**。</p>
<p>这些加密操作符允许您声明指向成员的指针或访问它。此类指针的类型包括返回类型、拥有成员的类、每个实参的类型以及函数的const-ness。</p>
<p>*<em>你不能真正地将指向成员函数的指针转换为任何东西，特别是不能转换为void</em>，因为它们有不同的<code>sizeof</code>**。</p>
<p>如果函数的签名略有不同，则不能从一个函数转换到另一个函数。例如，甚至不允许从<code>void (MyClass::*)(int) const</code>转换为<code>void (MyClass::*)(int)</code>。(你可以用reinterpret_cast;但根据标准，如果你调用它们，那将是一个未定义的行为)</p>
<p>指向成员函数的指针不同于普通的函数指针。普通的函数指针只是一个普通的指针，指向该函数代码所在的地址。但是**指向成员函数的指针需要存储更多的信息:成员函数可以是虚的，在多重继承的情况下，还需要对隐藏的this应用偏移量(offset)**。</p>
<p><strong>指向成员函数的指针的sizeof大小甚至可以根据类的不同而变化</strong>。这就是为什么我们在操纵它们时需要特别小心。</p>
<h2 id="Type-Traits-QtPrivate-FunctionPointer-rocket"><a href="#Type-Traits-QtPrivate-FunctionPointer-rocket" class="headerlink" title="Type Traits: QtPrivate::FunctionPointer:rocket:"></a>Type Traits: <code>QtPrivate::FunctionPointer</code>:rocket:</h2><p>让我向你介绍<code>QtPrivate::FunctionPointer</code>类型的trait。</p>
<p>trait基本上是一个helper类，它提供关于给定类型的元数据。Qt中trait的另一个例子是<code>QTypeInfo</code>。</p>
<p>为了实现新语法，我们需要知道关于函数指针的信息。</p>
<p>模板<typename T> struct FunctionPointer将通过它的成员给我们关于T的信息。</p>
<ul>
<li><strong><code>ArgumentCount</code>:</strong> An integer representing the number of arguments of the function.</li>
<li><strong><code>Object</code>:</strong> Exists only for pointer to member function.它是函数所属类的类型定义。</li>
<li><strong><code>Arguments</code>:</strong> Represents the list of argument. It is a typedef to a meta-programming list.</li>
<li><strong><code>call(T &amp;function, QObject *receiver, void **args)</code>:</strong> A static function that will call the function, applying the given parameters.</li>
</ul>
<p>Qt仍然支持c++ 98编译器，这意味着我们不需要支持可变参数模板。因此，我们必须为每个参数的数量特化我们的trait函数。我们有四种特化:普通函数指针、指向成员函数的指针、指向const成员函数的指针和仿函。对于每一种类型，我们都需要针对每个数量的参数进行特化。我们最多支持六个参数。我们还使用可变参数模板进行了特化，因此如果编译器支持可变参数模板，我们就支持任意数量的参数。</p>
<p>The implementation of <code>FunctionPointer</code> lies in <a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs_impl.h.html#QtPrivate::FunctionPointer">qobjectdefs_impl.h</a>.</p>
<h2 id="QObject-connect"><a href="#QObject-connect" class="headerlink" title="QObject::connect"></a><code>QObject::connect</code></h2><p>The implementation relies on a lot of template code. I am not going to explain all of it.</p>
<p>Here is the code of the first new overload from <a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobject.h.html#_ZN7QObject7connectEPKN9QtPrivate15FunctionPointerIT_E6ObjectES2_PKNS1_IT0_E6ObjectES7_N2Qt14ConnectionTypeE">qobject.h</a>:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> Func1, <span class="keyword">typename</span> Func2&gt;</span><br><span class="line"><span class="function"><span class="type">static</span> <span class="keyword">inline</span> QMetaObject::Connection <span class="title">connect</span><span class="params">(</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="type">const</span> <span class="keyword">typename</span> QtPrivate::FunctionPointer&lt;Func1&gt;::Object *sender, Func1 signal,</span></span></span><br><span class="line"><span class="params"><span class="function">    <span class="type">const</span> <span class="keyword">typename</span> QtPrivate::FunctionPointer&lt;Func2&gt;::Object *receiver, Func2 slot,</span></span></span><br><span class="line"><span class="params"><span class="function">    Qt::ConnectionType type = Qt::AutoConnection)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="keyword">typedef</span> QtPrivate::FunctionPointer&lt;Func1&gt; SignalType;</span><br><span class="line">  <span class="keyword">typedef</span> QtPrivate::FunctionPointer&lt;Func2&gt; SlotType;</span><br><span class="line"></span><br><span class="line">  <span class="comment">//compilation error if the arguments does not match.</span></span><br><span class="line">  <span class="built_in">Q_STATIC_ASSERT_X</span>(<span class="built_in">int</span>(SignalType::ArgumentCount) &gt;= <span class="built_in">int</span>(SlotType::ArgumentCount),</span><br><span class="line">                    <span class="string">&quot;The slot requires more arguments than the signal provides.&quot;</span>);</span><br><span class="line">  <span class="built_in">Q_STATIC_ASSERT_X</span>((QtPrivate::CheckCompatibleArguments&lt;<span class="keyword">typename</span> SignalType::Arguments,</span><br><span class="line">                                                         <span class="keyword">typename</span> SlotType::Arguments&gt;::value),</span><br><span class="line">                    <span class="string">&quot;Signal and slot arguments are not compatible.&quot;</span>);</span><br><span class="line">  <span class="built_in">Q_STATIC_ASSERT_X</span>((QtPrivate::AreArgumentsCompatible&lt;<span class="keyword">typename</span> SlotType::ReturnType,</span><br><span class="line">                                                       <span class="keyword">typename</span> SignalType::ReturnType&gt;::value),</span><br><span class="line">                    <span class="string">&quot;Return type of the slot is not compatible with the return type of the signal.&quot;</span>);<span class="comment">// 信号和槽的returnType只能为void类型</span></span><br><span class="line"></span><br><span class="line">  <span class="type">const</span> <span class="type">int</span> *types;</span><br><span class="line">  <span class="comment">/* ... Skipped initialization of types, used for QueuedConnection ...*/</span></span><br><span class="line"></span><br><span class="line">  QtPrivate::QSlotObjectBase *slotObj = <span class="keyword">new</span> QtPrivate::QSlotObject&lt;Func2,</span><br><span class="line">        <span class="keyword">typename</span> QtPrivate::List_Left&lt;<span class="keyword">typename</span> SignalType::Arguments, SlotType::ArgumentCount&gt;::Value,</span><br><span class="line">        <span class="keyword">typename</span> SignalType::ReturnType&gt;(slot);</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">connectImpl</span>(sender, <span class="built_in">reinterpret_cast</span>&lt;<span class="type">void</span> **&gt;(&amp;signal),</span><br><span class="line">                     receiver, <span class="built_in">reinterpret_cast</span>&lt;<span class="type">void</span> **&gt;(&amp;slot), slotObj,</span><br><span class="line">                     type, types, &amp;SignalType::Object::staticMetaObject);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>您注意到，在函数签名中，<code>sender</code>和<code>receiver</code>并不像文档中指出的那样仅仅是<code>QObject*</code>。它们是指向<code>typename FunctionPointer::Object</code>的指针。这使用<a target="_blank" rel="noopener" href="http://en.wikipedia.org/wiki/Substitution_failure_is_not_an_error">SFINAE</a>使重载仅对指向成员函数的指针启用，因为如果对象类型是指向成员函数的指针，则该对象仅存在于FunctionPointer中。</p>
<p>然后我们从一堆Q_STATIC_ASSERT开始。当用户犯错误时，它们应该生成合理的编译错误消息。如果用户做错了什么，重要的是让他&#x2F;她在这里看到错误，而不是在_impl.h文件中的模板代码中看到错误。我们希望<strong>对不需要关心底层实现的用户隐藏底层实现</strong>。<br>这意味着，如果您在实现细节中看到令人困惑的错误，则应将其视为应该报告的错误。</p>
<p>然后我们分配一个<code>QSlotObject</code>，它将被传递给<code>connectImpl()</code>。QSlotObject是插槽的包装器，有助于调用它。它还知道信号参数的类型，因此可以进行适当的类型转换。</p>
<p>我们使用<code>List_Left</code>只传递与slot相同数量的参数，这<strong>允许将具有许多参数的信号连接到具有较少参数的slot</strong>。</p>
<p><code>QObject::connectImpl</code>是执行连接的私有内部函数。它与原始语法相似，不同之处在于，我们存储的不是在<code>QObjectPrivate::Connection</code>结构中存储方法索引，而是指向<code>QSlotObjectBase</code>的指针。</p>
<p>之所以将<code>&amp;slot</code>传递为<code>void**</code>，只是为了在类型为<code>Qt::UniqueConnection</code>时能够比较它。</p>
<p>我们还将<code>&amp;signal</code>传递为<code>void**</code>。它是指向成员函数指针的指针。(是的，指向指针的指针)</p>
<h2 id="Signal-Index-fire"><a href="#Signal-Index-fire" class="headerlink" title="Signal Index:fire:"></a>Signal Index:fire:</h2><p>我们需要在信号指针和信号下标之间建立一个关系。</p>
<p>我们使用MOC。是的，这意味着这个新的语法仍在使用MOC，并且没有计划摆脱它:-)。</p>
<p>MOC将在<code>qt_static_metacall</code>中生成比较参数并返回正确索引的代码。<code>connectImpl</code>将使用指向函数指针的指针调用qt_static_metacall函数。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">Counter::qt_static_metacall</span><span class="params">(QObject *_o, QMetaObject::Call _c, <span class="type">int</span> _id, <span class="type">void</span> **_a)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (_c == QMetaObject::InvokeMetaMethod) &#123;</span><br><span class="line">        <span class="comment">/* .... skipped ....*/</span></span><br><span class="line">        <span class="comment">// 根据_id决定调用哪个参数，_id从小到大依次为信号，槽函数，其他函数</span></span><br><span class="line">        <span class="keyword">default</span>: ;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125; <span class="keyword">else</span> <span class="keyword">if</span> (_c == QMetaObject::IndexOfMethod) &#123;</span><br><span class="line">    	<span class="comment">// 方法索引方式的调用，根据强转是否成功确定真正的方法索引，即_id</span></span><br><span class="line">        <span class="type">int</span> *result = <span class="built_in">reinterpret_cast</span>&lt;<span class="type">int</span> *&gt;(_a[<span class="number">0</span>]);</span><br><span class="line">        <span class="type">void</span> **func = <span class="built_in">reinterpret_cast</span>&lt;<span class="type">void</span> **&gt;(_a[<span class="number">1</span>]);</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(Counter::*<span class="type">_t</span>)</span><span class="params">(<span class="type">int</span> )</span></span>;</span><br><span class="line">            <span class="keyword">if</span> (*<span class="built_in">reinterpret_cast</span>&lt;<span class="type">_t</span> *&gt;(func) == <span class="built_in">static_cast</span>&lt;<span class="type">_t</span>&gt;(&amp;Counter::valueChanged)) &#123;</span><br><span class="line">                *result = <span class="number">0</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="function"><span class="keyword">typedef</span> <span class="title">QString</span> <span class="params">(Counter::*<span class="type">_t</span>)</span><span class="params">(<span class="type">const</span> QString &amp; )</span></span>;</span><br><span class="line">            <span class="keyword">if</span> (*<span class="built_in">reinterpret_cast</span>&lt;<span class="type">_t</span> *&gt;(func) == <span class="built_in">static_cast</span>&lt;<span class="type">_t</span>&gt;(&amp;Counter::someOtherSignal)) &#123;</span><br><span class="line">                *result = <span class="number">1</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(Counter::*<span class="type">_t</span>)</span><span class="params">()</span></span>;</span><br><span class="line">            <span class="keyword">if</span> (*<span class="built_in">reinterpret_cast</span>&lt;<span class="type">_t</span> *&gt;(func) == <span class="built_in">static_cast</span>&lt;<span class="type">_t</span>&gt;(&amp;Counter::anotherSignal)) &#123;</span><br><span class="line">                *result = <span class="number">2</span>;</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br></pre></td></tr></table></figure>

<p>一旦我们有了信号索引，我们就可以像在其他语法中那样处理。</p>
<h2 id="The-QSlotObjectBase"><a href="#The-QSlotObjectBase" class="headerlink" title="The QSlotObjectBase"></a>The QSlotObjectBase</h2><p>QSlotObjectBase是传递给代表（represent）插槽的connectImpl的对象。</p>
<p>在显示实际代码之前，这是QObject::QSlotObjectBase在Qt5 alpha中的内容:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">struct</span> <span class="title class_">QSlotObjectBase</span> &#123;</span><br><span class="line">    QAtomicInt ref;</span><br><span class="line">    <span class="built_in">QSlotObjectBase</span>() : <span class="built_in">ref</span>(<span class="number">1</span>) &#123;&#125;</span><br><span class="line">    <span class="keyword">virtual</span> ~<span class="built_in">QSlotObjectBase</span>();</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">void</span> <span class="title">call</span><span class="params">(QObject *receiver, <span class="type">void</span> **a)</span> </span>= <span class="number">0</span>;</span><br><span class="line">    <span class="function"><span class="keyword">virtual</span> <span class="type">bool</span> <span class="title">compare</span><span class="params">(<span class="type">void</span> **)</span> </span>&#123; <span class="keyword">return</span> <span class="literal">false</span>; &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>它基本上是一个接口，由实现函数指针调用和比较的模板类重新实现。</p>
<p>它由QSlotObject、QStaticSlotObject或QFunctorSlotObject模板类中的一个重新实现。</p>
<h3 id="Fake-Virtual-Table"><a href="#Fake-Virtual-Table" class="headerlink" title="Fake Virtual Table"></a>Fake Virtual Table</h3><p>这样做的问题是，这些对象的每次实例化都需要创建一个虚拟表，其中不仅包含指向虚函数的指针，还包含许多我们不需要的信息，例如RTTI。这将导致大量多余的数据和二进制文件中的重定位。</p>
<p>为了避免这种情况，QSlotObjectBase被更改为不是c++多态类。虚函数是手工模拟的。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">QSlotObjectBase</span> &#123;</span><br><span class="line">  QAtomicInt m_ref;</span><br><span class="line">  <span class="function"><span class="keyword">typedef</span> <span class="title">void</span> <span class="params">(*ImplFn)</span><span class="params">(<span class="type">int</span> which, QSlotObjectBase* this_,</span></span></span><br><span class="line"><span class="params"><span class="function">                         QObject *receiver, <span class="type">void</span> **args, <span class="type">bool</span> *ret)</span></span>;</span><br><span class="line">  <span class="type">const</span> ImplFn m_impl;</span><br><span class="line"><span class="keyword">protected</span>:</span><br><span class="line">  <span class="keyword">enum</span> <span class="title class_">Operation</span> &#123; Destroy, Call, Compare &#125;;</span><br><span class="line"><span class="keyword">public</span>:</span><br><span class="line">  <span class="function"><span class="keyword">explicit</span> <span class="title">QSlotObjectBase</span><span class="params">(ImplFn fn)</span> : m_ref(<span class="number">1</span>), m_impl(fn) &#123;</span>&#125;</span><br><span class="line">  <span class="function"><span class="keyword">inline</span> <span class="type">int</span> <span class="title">ref</span><span class="params">()</span> Q_DECL_NOTHROW </span>&#123; <span class="keyword">return</span> m_ref.<span class="built_in">ref</span>(); &#125;</span><br><span class="line">  <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">destroyIfLastRef</span><span class="params">()</span> Q_DECL_NOTHROW </span>&#123;</span><br><span class="line">    <span class="keyword">if</span> (!m_ref.<span class="built_in">deref</span>()) <span class="built_in">m_impl</span>(Destroy, <span class="keyword">this</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="function"><span class="keyword">inline</span> <span class="type">bool</span> <span class="title">compare</span><span class="params">(<span class="type">void</span> **a)</span> </span>&#123; <span class="type">bool</span> ret; <span class="built_in">m_impl</span>(Compare, <span class="keyword">this</span>, <span class="number">0</span>, a, &amp;ret); <span class="keyword">return</span> ret; &#125;</span><br><span class="line">  <span class="function"><span class="keyword">inline</span> <span class="type">void</span> <span class="title">call</span><span class="params">(QObject *r, <span class="type">void</span> **a)</span> </span>&#123;  <span class="built_in">m_impl</span>(Call,    <span class="keyword">this</span>, r, a, <span class="number">0</span>); &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

<p>m_impl是一个(普通的)函数指针，它执行以前是虚函数的三个操作。“重新实现”在构造函数中将其设置为自己的实现。</p>
<p>请不要进入你的代码，用这样的黑客来替换你所有的虚函数，因为你在这里读到它很好。这只是在这种情况下做的，因为几乎每个调用连接都会生成一个新的不同类型(因为QSlotObject有模板参数，依赖于信号和插槽的签名)。</p>
<h2 id="Protected-Public-or-Private-Signals"><a href="#Protected-Public-or-Private-Signals" class="headerlink" title="Protected, Public, or Private Signals."></a>Protected, Public, or Private Signals.</h2><p>Qt4及之前对信号进行保护。这是一种设计选择，因为当对象改变其状态时，应该发出信号。它们不应该从对象外部发出，在另一个对象上调用信号几乎总是一个坏主意。</p>
<p>但是，使用新的语法，您需要能够从您建立连接的点获取信号的地址。编译器只会让你这样做，如果你能访问那个信号。如果信号不是公共的，写入&amp;Counter::valueChanged将产生编译器错误。</p>
<p>在Qt 5中，我们必须将信号从受保护的转换为公共的。这是不幸的，因为这意味着任何人都可以发出信号。我们没有办法绕过它。我们用emit关键字尝试了一个技巧。我们尝试返回一个特殊值。但是什么都不管用。我相信新语法的优点克服了信号现在是公开的问题。</p>
<p><em>有时甚至需要将信号设置为私有。例如，在QAbstractItemModel中就是这样，否则，开发人员倾向于从派生类发出信号，这不是API想要的</em>。以前有一个预处理器技巧，使信号私有，但它破坏了新的连接语法。</p>
<p>一种新的hack出现了。<code>QPrivateSignal</code>是一个在Q_OBJECT宏中声明为私有的虚拟(空)结构。它可以作为信号的最后一个参数。因为它是私有的，所以只有对象有权构造它来调用信号。MOC在生成签名信息时将忽略QPrivateSignal最后一个参数。有关示例，请参阅<a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/itemmodels/qabstractitemmodel.h.html#_ZN18QAbstractItemModel21rowsAboutToBeInsertedERK11QModelIndexiiNS_14QPrivateSignalE">qabstractitemmodel.h</a>。</p>
<h2 id="More-Template-Code"><a href="#More-Template-Code" class="headerlink" title="More Template Code"></a>More Template Code</h2><p>The rest of the code is in <a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs_impl.h.html">qobjectdefs_impl.h</a> and <a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobject_impl.h.html">qobject_impl.h</a>. 它主要是标准的枯燥的模板代码。</p>
<p>在这篇文章中，我不会讲得太详细，但我只讲几个值得一提的项目。</p>
<h3 id="Meta-Programming-List-fire"><a href="#Meta-Programming-List-fire" class="headerlink" title="Meta-Programming List:fire:"></a>Meta-Programming List:fire:</h3><p>如前所述，FunctionPointer::Arguments是参数列表。代码需要对列表进行操作:遍历每个元素，只取其中的一部分或选择给定的项。</p>
<p>这就是为什么有<a href="QtPrivate::List"><code>QtPrivate::List</code></a>可以表示类型列表。一些操作它的辅助工具是<a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs_impl.h.html#QtPrivate::List_Select"><code>QtPrivate::List_Select</code></a>和<a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs_impl.h.html#QtPrivate::List_Left"><code>QtPrivate::List_Left</code></a>，它们给出列表中的第N个元素和包含前N个元素的子列表。</p>
<p>支持可变参数模板的编译器和不支持可变参数模板的编译器，List的实现是不同的。</p>
<p>对于可变参数模板，它是一个模板<code>&lt;typename…T&gt; struct List;</code>。参数列表只是封装在模板形参中。<br>例如:包含参数<code>(int, QString, QObject*)</code>的列表类型将简单地为:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List&lt;<span class="type">int</span>, QString, QObject *&gt;</span><br></pre></td></tr></table></figure>

<p>如果没有可变参数模板，它是一个lisp风格的列表:<code>template&lt;typename Head, typename Tail &gt; struct list;</code>其中Tail可以是另一个List，也可以是列表末尾的void。</p>
<p>与前面相同的例子是:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List&lt;<span class="type">int</span>, List&lt;QString, List&lt;QObject *, <span class="type">void</span>&gt; &gt; &gt;</span><br></pre></td></tr></table></figure>

<h3 id="ApplyReturnValue-Trick"><a href="#ApplyReturnValue-Trick" class="headerlink" title="ApplyReturnValue Trick"></a>ApplyReturnValue Trick</h3><p>在函数<code>QtPrivate::FunctionPointer::call</code>中，args[0]意味着接收插槽的返回值。如果该信号返回一个值，则该值是指向该信号返回类型的对象的指针，否则为0。如果slot返回一个值，我们需要将它复制到arg[0]中。如果它返回void，我们什么也不做。</p>
<p>问题是<strong>使用返回void的函数的返回值在语法上是不正确的</strong>。我应该复制已经大量复制的代码（一次为void返回类型，另一次为非void）吗?不，多亏了逗号运算符。</p>
<p>在c++中，你可以这样做:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">functionThatReturnsVoid</span>(), <span class="built_in">somethingElse</span>();</span><br></pre></td></tr></table></figure>

<p>你可以用分号(semicolon)代替逗号(comma)，一切都没问题。</p>
<p>有趣的是当你用非空的东西调用它时:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">functionThatReturnsInt</span>(), <span class="built_in">somethingElse</span>();</span><br></pre></td></tr></table></figure>

<p>There, the comma will actually call an operator that you even can overload. It is what we do in <a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobjectdefs_impl.h.html#QtPrivate::ApplyReturnValue">qobjectdefs_impl.h</a></p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">template</span> &lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="keyword">struct</span> <span class="title class_">ApplyReturnValue</span> &#123;</span><br><span class="line">    <span class="type">void</span> *data;</span><br><span class="line">    <span class="built_in">ApplyReturnValue</span>(<span class="type">void</span> *data_) : <span class="built_in">data</span>(data_) &#123;&#125;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T, <span class="keyword">typename</span> U&gt;</span><br><span class="line"><span class="type">void</span> <span class="keyword">operator</span>,(<span class="type">const</span> T &amp;value, <span class="type">const</span> ApplyReturnValue&lt;U&gt; &amp;container) &#123;</span><br><span class="line">    <span class="keyword">if</span> (container.data)</span><br><span class="line">        *<span class="built_in">reinterpret_cast</span>&lt;U*&gt;(container.data) = value;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">template</span>&lt;<span class="keyword">typename</span> T&gt;</span><br><span class="line"><span class="type">void</span> <span class="keyword">operator</span>,(T, <span class="type">const</span> ApplyReturnValue&lt;<span class="type">void</span>&gt; &amp;) &#123;&#125;</span><br></pre></td></tr></table></figure>

<p>ApplyReturnValue只是对<code>void*</code>的包装。然后可以在每个helper中使用它。这是一个没有参数的仿函的例子:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">call</span><span class="params">(Function &amp;f, <span class="type">void</span> *, <span class="type">void</span> **arg)</span> </span>&#123;</span><br><span class="line">      <span class="built_in">f</span>(), <span class="built_in">ApplyReturnValue</span>&lt;SignalReturnType&gt;(arg[<span class="number">0</span>]);</span><br><span class="line">  &#125;</span><br></pre></td></tr></table></figure>

<p>这段代码是内联的，所以在运行时不会产生任何开销。</p>
<h1 id="Queued-and-Inter-Thread-Connections"><a href="#Queued-and-Inter-Thread-Connections" class="headerlink" title="Queued and Inter Thread Connections"></a>Queued and Inter Thread Connections</h1><h2 id="Summary-from-Part-1"><a href="#Summary-from-Part-1" class="headerlink" title="Summary from Part 1"></a>Summary from Part 1</h2><p>在第一部分中，我们看到信号只是简单的函数，其主体是由moc生成的。它们只是调用<code>QMetaObject::activate</code>，并<strong>使用指向堆栈上参数的指针数组</strong>。下面是一个信号的代码，由moc生成:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// SIGNAL 0</span></span><br><span class="line"><span class="function"><span class="type">void</span> <span class="title">Counter::valueChanged</span><span class="params">(<span class="type">int</span> _t1)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="type">void</span> *_a[] = &#123; Q_NULLPTR, <span class="built_in">const_cast</span>&lt;<span class="type">void</span>*&gt;(<span class="built_in">reinterpret_cast</span>&lt;<span class="type">const</span> <span class="type">void</span>*&gt;(&amp;_t1)) &#125;;</span><br><span class="line">    QMetaObject::<span class="built_in">activate</span>(<span class="keyword">this</span>, &amp;staticMetaObject, <span class="number">0</span>, _a);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p><a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobject.cpp.html#_ZN11QMetaObject8activateEP7QObjectiiPPv"><code>QMetaObject::activate</code></a>然后将在内部数据结构中查找连接到该信号的槽。如第1部分所示，对于每个插槽，将执行以下代码:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Determine if this connection should be sent immediately or</span></span><br><span class="line"><span class="comment">// put into the event queue</span></span><br><span class="line"><span class="keyword">if</span> ((c-&gt;connectionType == Qt::AutoConnection &amp;&amp; !receiverInSameThread)</span><br><span class="line">        || (c-&gt;connectionType == Qt::QueuedConnection)) &#123;</span><br><span class="line">    <span class="built_in">queued_activate</span>(sender, signal_index, c, argv, locker);</span><br><span class="line">    <span class="keyword">continue</span>;</span><br><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (c-&gt;connectionType == Qt::BlockingQueuedConnection) &#123;</span><br><span class="line">    <span class="comment">/* ... Skipped ... */</span></span><br><span class="line">    <span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">/* ... DirectConnection: call the slot as seen in Part 1 */</span></span><br></pre></td></tr></table></figure>

<p>因此，在这篇博文中，我们将看到在<code>queued_activate</code>和<code>BlockingQueuedConnection</code>中跳过的其他部分中究竟发生了什么</p>
<h2 id="Qt-Event-Loop-fire"><a href="#Qt-Event-Loop-fire" class="headerlink" title="Qt Event Loop:fire:"></a>Qt Event Loop:fire:</h2><p><code>QueuedConnection</code>将向事件循环提交一个事件以最终被处理。</p>
<p>当发布一个事件时(在<code>QCoreApplication::postEvent</code>中)，该事件将被推送到每个线程队列中(<code>QThreadData::postEventList</code>)。事件队列由互斥锁保护，因此当线程将事件推送到另一个线程的事件队列时，不存在竞争条件。</p>
<p>一旦事件被添加到队列中，并且如果接收者位于另一个线程中，我们通过调用<code>QAbstractEventDispatcher::wakeUp</code>通知该线程的事件调度程序。如果它在等待更多事件时处于睡眠状态，这将唤醒调度程序。如果接收者在同一个线程中，事件将在事件循环迭代时稍后处理。</p>
<p>事件将在处理它的线程中被处理后立即删除。</p>
<p><strong>使用<code>QueuedConnection</code>发布的事件是<code>QMetaCallEvent</code>。在处理时，该事件将以与直接连接相同的方式调用插槽</strong>。所有信息(要调用的槽位、参数值等等)都存储在事件中。</p>
<h2 id="Copying-the-parameters"><a href="#Copying-the-parameters" class="headerlink" title="Copying the parameters"></a>Copying the parameters</h2><p>来自信号的<code>argv</code>是一个指向参数的指针数组。问题是，这些指针指向了所在的信号参数所在的栈。一旦信号返回，它们将不再有效。所以我们要把函数的参数值复制到堆上。为了做到这一点，我们只需要问QMetaType。我们已经在[QMetaType knows your types](<a target="_blank" rel="noopener" href="https://woboq.com/blog/qmetatype-knows-your-types.html">QMetaType knows your types (woboq.com)</a>)文章中看到，<code>QMetaType::create</code>能够复制任何类型，知道它是QMetaType ID和指向该类型的指针。</p>
<p>要知道特定参数的QMetaType ID，我们将在QMetaObject中查找，它包含所有类型的名称。然后，我们将能够在QMetaType数据库中查找特定类型。</p>
<h2 id="queued-activate"><a href="#queued-activate" class="headerlink" title="queued_activate"></a>queued_activate</h2><p>现在我们可以把它们放在一起并通读<a target="_blank" rel="noopener" href="https://code.woboq.org/qt5/qtbase/src/corelib/kernel/qobject.cpp.html#_ZL15queued_activateP7QObjectiPN14QObjectPrivate10ConnectionEPPvR12QMutexLocker">queued_activate</a>的代码，它由<code>QMetaObject::activate</code>调用，以准备<code>Qt::QueuedConnection</code>插槽调用。这里显示的代码经过了稍微简化和注释:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">static</span> <span class="type">void</span> <span class="title">queued_activate</span><span class="params">(QObject *sender, <span class="type">int</span> signal,</span></span></span><br><span class="line"><span class="params"><span class="function">                            QObjectPrivate::Connection *c, <span class="type">void</span> **argv,</span></span></span><br><span class="line"><span class="params"><span class="function">                            QMutexLocker &amp;locker)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">  <span class="type">const</span> <span class="type">int</span> *argumentTypes = c-&gt;argumentTypes;</span><br><span class="line">  <span class="comment">// c-&gt;argumentTypes is an array of int containing the argument types.</span></span><br><span class="line">  <span class="comment">// It might have been initialized in the connection statement when using the</span></span><br><span class="line">  <span class="comment">// new syntax, but usually it is `nullptr` until the first queued activation</span></span><br><span class="line">  <span class="comment">// of that connection.</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// DIRECT_CONNECTION_ONLY is a dummy int which means that there was an error</span></span><br><span class="line">  <span class="comment">// fetching the type ID of the arguments.</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!argumentTypes) &#123;</span><br><span class="line">    <span class="comment">// Ask the QMetaObject for the parameter names, and use the QMetaType</span></span><br><span class="line">    <span class="comment">// system to look up type IDs</span></span><br><span class="line">    QMetaMethod m = QMetaObjectPrivate::<span class="built_in">signal</span>(sender-&gt;<span class="built_in">metaObject</span>(), signal);</span><br><span class="line">    argumentTypes = <span class="built_in">queuedConnectionTypes</span>(m.<span class="built_in">parameterTypes</span>());</span><br><span class="line">    <span class="keyword">if</span> (!argumentTypes) <span class="comment">// Cannot queue arguments</span></span><br><span class="line">      argumentTypes = &amp;DIRECT_CONNECTION_ONLY;</span><br><span class="line">    c-&gt;argumentTypes = argumentTypes; <span class="comment">/* ... skipped: atomic update ... */</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">if</span> (argumentTypes == &amp;DIRECT_CONNECTION_ONLY) <span class="comment">// Cannot activate</span></span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">  <span class="type">int</span> nargs = <span class="number">1</span>; <span class="comment">// Include the return type</span></span><br><span class="line">  <span class="keyword">while</span> (argumentTypes[nargs<span class="number">-1</span>])</span><br><span class="line">      ++nargs;</span><br><span class="line">  <span class="comment">// Copy the argumentTypes array since the event is going to take ownership</span></span><br><span class="line">  <span class="type">int</span> *types = (<span class="type">int</span> *) <span class="built_in">malloc</span>(nargs*<span class="built_in">sizeof</span>(<span class="type">int</span>));</span><br><span class="line">  <span class="type">void</span> **args = (<span class="type">void</span> **) <span class="built_in">malloc</span>(nargs*<span class="built_in">sizeof</span>(<span class="type">void</span> *));</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Ignore the return value as it makes no sense in a queued connection</span></span><br><span class="line">  types[<span class="number">0</span>] = <span class="number">0</span>; <span class="comment">// Return type</span></span><br><span class="line">  args[<span class="number">0</span>] = <span class="number">0</span>; <span class="comment">// Return value</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (nargs &gt; <span class="number">1</span>) &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> n = <span class="number">1</span>; n &lt; nargs; ++n)</span><br><span class="line">      types[n] = argumentTypes[n<span class="number">-1</span>];</span><br><span class="line"></span><br><span class="line">    <span class="comment">// We must unlock the object&#x27;s signal mutex while calling the copy</span></span><br><span class="line">    <span class="comment">// constructors of the arguments as they might re-enter and cause a deadlock</span></span><br><span class="line">    locker.<span class="built_in">unlock</span>();</span><br><span class="line">    <span class="keyword">for</span> (<span class="type">int</span> n = <span class="number">1</span>; n &lt; nargs; ++n)</span><br><span class="line">      args[n] = QMetaType::<span class="built_in">create</span>(types[n], argv[n]);</span><br><span class="line">    locker.<span class="built_in">relock</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (!c-&gt;receiver) &#123;</span><br><span class="line">      <span class="comment">// We have been disconnected while the mutex was unlocked</span></span><br><span class="line">      <span class="comment">/* ... skipped cleanup ... */</span></span><br><span class="line">      <span class="keyword">return</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Post an event</span></span><br><span class="line">  QMetaCallEvent *ev = c-&gt;isSlotObject ?</span><br><span class="line">    <span class="keyword">new</span> <span class="built_in">QMetaCallEvent</span>(c-&gt;slotObj, sender, signal, nargs, types, args) :</span><br><span class="line">    <span class="keyword">new</span> <span class="built_in">QMetaCallEvent</span>(c-&gt;method_offset, c-&gt;method_relative, c-&gt;callFunction,</span><br><span class="line">                       sender, signal, nargs, types, args);</span><br><span class="line">  QCoreApplication::<span class="built_in">postEvent</span>(c-&gt;receiver, ev);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在接收到此事件时，<code>QObject::event</code>将设置发送方并调用<code>QMetaCallEvent::placeMetaCall</code>。后面的函数将以与<code>QMetaObject::activate</code>对直接连接相同的方式进行分派，如第1部分所述</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">case</span> QEvent::MetaCall:</span><br><span class="line">&#123;</span><br><span class="line">    QMetaCallEvent *mce = <span class="built_in">static_cast</span>&lt;QMetaCallEvent*&gt;(e);</span><br><span class="line"></span><br><span class="line">    <span class="function">QConnectionSenderSwitcher <span class="title">sw</span><span class="params">(<span class="keyword">this</span>, <span class="keyword">const_cast</span>&lt;QObject*&gt;(mce-&gt;sender()),</span></span></span><br><span class="line"><span class="params"><span class="function">                                 mce-&gt;signalId())</span></span>;</span><br><span class="line"></span><br><span class="line">    mce-&gt;<span class="built_in">placeMetaCall</span>(<span class="keyword">this</span>);</span><br><span class="line">    <span class="keyword">break</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="BlockingQueuedConnection"><a href="#BlockingQueuedConnection" class="headerlink" title="BlockingQueuedConnection"></a>BlockingQueuedConnection</h2><p><code>BlockingQueuedConnection</code>是<code>DirectConnection</code>和<code>QueuedConnection</code>的混合。像DirectConnection一样，参数可以留在栈上，因为栈在被阻塞的线程上。不需要复制参数。与QueuedConnection类似，事件被发布到另一个线程的事件循环中。该事件还包含一个指向<code>QSemaphore</code>的指针。<strong>传递事件的线程将在插槽被调用后立即释放信号量。同时，调用该信号的线程将获取信号量，以便等待事件被处理</strong>。</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">&#125; <span class="keyword">else</span> <span class="keyword">if</span> (c-&gt;connectionType == Qt::BlockingQueuedConnection) &#123;</span><br><span class="line">  locker.<span class="built_in">unlock</span>(); <span class="comment">// unlock the QObject&#x27;s signal mutex.</span></span><br><span class="line">  <span class="keyword">if</span> (receiverInSameThread) &#123;</span><br><span class="line">    <span class="built_in">qWarning</span>(<span class="string">&quot;Qt: Dead lock detected while activating a BlockingQueuedConnection: &quot;</span></span><br><span class="line">             <span class="string">&quot;Sender is %s(%p), receiver is %s(%p)&quot;</span>,</span><br><span class="line">             sender-&gt;<span class="built_in">metaObject</span>()-&gt;<span class="built_in">className</span>(), sender,</span><br><span class="line">             receiver-&gt;<span class="built_in">metaObject</span>()-&gt;<span class="built_in">className</span>(), receiver);</span><br><span class="line">  &#125;</span><br><span class="line">  QSemaphore semaphore;</span><br><span class="line">  QMetaCallEvent *ev = c-&gt;isSlotObject ?</span><br><span class="line">    <span class="keyword">new</span> <span class="built_in">QMetaCallEvent</span>(c-&gt;slotObj, sender, signal_index, <span class="number">0</span>, <span class="number">0</span>, argv, &amp;semaphore) :</span><br><span class="line">    <span class="keyword">new</span> <span class="built_in">QMetaCallEvent</span>(c-&gt;method_offset, c-&gt;method_relative, c-&gt;callFunction,</span><br><span class="line">                       sender, signal_index, <span class="number">0</span>, <span class="number">0</span>, argv , &amp;semaphore);</span><br><span class="line">  QCoreApplication::<span class="built_in">postEvent</span>(receiver, ev);</span><br><span class="line">  semaphore.<span class="built_in">acquire</span>();</span><br><span class="line">  locker.<span class="built_in">relock</span>();</span><br><span class="line">  <span class="keyword">continue</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>它是QMetaCallEvent的析构函数，它将释放信号量。这很好，因为<strong>事件会在它被传递后(即插槽被调用)立即被删除，但当事件没有被传递时(例如，因为接收对象被删除)也会被删除</strong>。</p>
<p>当您希望在另一个线程中调用一个函数并在它完成之前等待答案时，<code>BlockingQueuedConnection</code>可以用于进行线程通信。然而，必须小心行事。</p>
<h3 id="The-dangers-of-BlockingQueuedConnection"><a href="#The-dangers-of-BlockingQueuedConnection" class="headerlink" title="The dangers of BlockingQueuedConnection"></a>The dangers of BlockingQueuedConnection</h3><p>为了避免死锁，您必须非常小心。</p>
<p>显然，如果使用同一个线程上的<code>BlockingQueuedConnection</code>连接两个对象，就会立即死锁。您正在将事件发送到发送方自己的线程，然后锁定线程等待事件被处理。由于线程被阻塞，事件将永远不会被处理，线程将永远被阻塞。<em>Qt在运行时检测到这个问题并输出警告，但不会尝试为您修复这个问题</em>。有人建议，如果两个对象都在同一个线程中，Qt可以只做一个普通的DirectConnection。但我们选择不这样做，因为<code>BlockingQueuedConnection</code>只有在您知道自己在做什么的情况下才能使用:您必须知道事件将从哪个线程发送到哪个线程。</p>
<p>真正的危险在于，您必须保持这样的设计:如果在应用程序中，从线程a到线程B执行<code>BlockingQueuedConnection</code>，那么线程B永远不能等待线程a，否则将再次出现死锁。</p>
<p>当发出信号或调用<code>QMetaObject::invokeMethod()</code>时，你不能有任何锁住的互斥量，线程B可能也会尝试锁住。</p>
<p>当你需要使用<code>BlockingQueuedConnection</code>终止一个线程时，通常会出现一个问题，例如在下面的伪代码中:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">MyOperation::stop</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    m_thread-&gt;<span class="built_in">quit</span>();</span><br><span class="line">    m_thread-&gt;<span class="built_in">wait</span>(); <span class="comment">// Waits for the callee thread, might deadlock</span></span><br><span class="line">    <span class="built_in">cleanup</span>();</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Connected via a BlockingQueuedConnection</span></span><br><span class="line"><span class="function">Stuff <span class="title">MyOperation::slotGetSomeData</span><span class="params">(<span class="type">const</span> Key &amp;k)</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="keyword">return</span> m_data-&gt;<span class="built_in">get</span>(k);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>在这里不能只调用wait，因为子线程可能已经发出或即将发出信号，该信号将等待父线程，而父线程将不会返回其事件循环。**所有线程清理信息传输必须只在线程之间发布事件时发生，而不使用wait()**。一个更好的方法是:</p>
<figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="type">void</span> <span class="title">MyOperation::stop</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>&#123;</span><br><span class="line">    <span class="built_in">connect</span>(m_thread, &amp;QThread::finished, <span class="keyword">this</span>, &amp;MyOperation::cleanup);</span><br><span class="line">    m_thread-&gt;<span class="built_in">quit</span>();</span><br><span class="line">    <span class="comment">/* (note that we connected before calling quit to avoid a race) */</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>缺点是MyOperation::cleanup()现在是异步调用的，这可能会使设计复杂化。</p>
<hr>

        
            <div class="donate-container">
    <div class="donate-button">
        <button id="donate-button">赞赏</button>
    </div>
    <div class="donate-img-container hide" id="donate-img-container">
        <img id="donate-img" src="" data-src="/myblog/img/donate.jpg">
        <p> 感谢鼓励 </p>
    </div>
</div>
        
        <br />
        <div id="comment-container">
        </div>
        <div id="disqus_thread"></div>
        <div id="lv-container"></div>
        <div class="giscus"></div>
    </div>
</div>

    </div>
</div>


<footer class="footer">
    <ul class="list-inline text-center">
        
        

        

        

        
        <li>
            <a target="_blank"  href="https://github.com/samuel-24276">
                            <span class="fa-stack fa-lg">
                                <i class="iconfont icon-github"></i>
                            </span>
            </a>
        </li>
        

        

    </ul>
    
    <p>
        <span>/</span>
        
        <span><a target="_blank" rel="noopener" href="https://github.com/samuel-24276">samuel-24276&#39;s Page</a></span>
        <span>/</span>
        
        <span><a target="_blank" rel="noopener" href="https://beian.miit.gov.cn/">北京-未备案</a></span>
        <span>/</span>
        
        <span><a href="#">返回顶部</a></span>
        <span>/</span>
        
    </p>
    
    <p>
        <span id="busuanzi_container_site_pv">
            <span id="busuanzi_value_site_pv"></span>PV
        </span>
        <span id="busuanzi_container_site_uv">
            <span id="busuanzi_value_site_uv"></span>UV
        </span>
        Created By <a target="_blank" rel="noopener" href="https://hexo.io/">Hexo</a>  Theme <a target="_blank" rel="noopener" href="https://github.com/aircloud/hexo-theme-aircloud">AirCloud</a></p>
</footer>




</body>

<script>
    // We expose some of the variables needed by the front end
    window.hexo_search_path = "search.json"
    window.hexo_root = "/myblog/"
    window.isPost = true
</script>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>

<script src="/myblog/js/index.js"></script>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>






    <script type="text/javascript">
       (function() {
           if (typeof LivereTower === 'function') { return; }

           var j, d = document.getElementById('lv-container');

           d.setAttribute('data-id','city');
           d.setAttribute('data-uid' , 'MTAyMC80OTA5OC8yNTU5Mw==');

           j = document.createElement('script');
           j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
           j.async = true;

           d.appendChild(j);
       })();
    </script>
    <noscript>为正常使用来必力评论功能请激活JavaScript</noscript>
    </div>

</html>
